今天要來補強昨日缺少的拖曳功能和將整個頁面封裝成提供整合至接龍遊戲的元件發牌區。
首先將移牌區的部分調整成從原生HTML元素div替換成這幾天都在使用draggable元件,程式碼如下:
<!-- 移牌區 - 左邊水平疊牌最多三張 -->
<draggable :list="canTakeCards" group="pokers" itemKey="value" class="list-group" >
<template #item="{ element, index }">
<Card :value="element.value" :isOpen="element.isOpen" />
</template>
</draggable>
雖然移牌區感覺在加牌的時候應該要套用動畫,但無奈vue.draggable.next的transition-group有Bug存在且已被官方棄養,在issue也有許多類似問題issue with transition-group in composition API就不細講。
不論是Vue2使用的vue.draggable或Vue3使用的vue.draggable.next都是對SortableJS進行包裝的容器,基本上不滿意的話只能自己重封裝實現只是CP值不高,所以我不考慮這麼做。
這不代表選擇放棄動畫,打算以CSS來實現以下動畫效果。
首先調整樣板程式碼,主要是針對有牌時添加對應的class"card-back animtion",也順便將點擊事件@click移到外層div:
<!-- 發牌堆 -->
<div class="card-box">
<div class="card " style="visibility: hidden;">
<div style="visibility: visible; width: 100%;height: 100%;" @click="clickCard">
<Transition name="slide-left">
<div v-if="deckState == 'empty'">無牌可用</div>
<div v-else-if="deckState == 'full'" class="card">重新循環</div>
<div v-else-if="deckState == 'normal'" class="card-back animation"></div>
</Transition>
</div>
</div>
</div>
添加對應CSS實現點擊後往左快速位移一次的效果
@keyframes move-left {
from {
transform: translateX(0rem);
}
to {
transform: translateX(-100rem);
}
}
.card-back.animation:active {
animation: move-left 0.55s ease;
animation-iteration-count: 1;
}
添加對應CSS當滑鼠移到發牌堆上方使其左右搖晃的效果
@keyframes swing {
0% {
transform: rotate(-5deg);
}
50% {
transform: rotate(5deg);
}
100% {
transform: rotate(-5deg);
}
}
.card-back:hover {
cursor: pointer;
animation: swing 1s ease infinite;
}
在目錄 src/components 內新增檔案DealerArea.vue,並將DealerAreaView.vue的程式碼完全複製到新檔案DealerArea.vue,
但需要調整卡牌陣列deck從外部取得,所以DealerArea.vue程式碼需將變數deck改寫成以下形式:
// DealerArea.vue
const { deck } = defineProps(
{
deck: {
type: Array,
default: () => []
}
}
)
因為卡牌是在onMounted時才會初始化cardStacks內的每個陣列,
但不清楚為什麼初始化結束後,<DealerArea />仍顯示無牌可用。
/** DragDemo.vue */
<DealerArea :deck="cardStacks.delaerStacks" />
查詢網路資料後,才知道原來子組件props如果會整個異動的話需要用watch去監控變化才會重新觸發渲染,
所以再次調整DealerArea.vue的程式碼:
// DealerArea.vue
const props = defineProps(
{
deck: {
type: Array,
default: () => []
},
moveCard: {
type: Function,
default: () => { return false; }
}
}
)
const deck = ref([]);
watch(() => props.deck, (newVal) => {
deck.value = newVal;
});
添加上方程式碼後,這裡<DealerArea>已可以根據props.deck的變動去做相對應的畫面更新。
但可以注意到defineProps突然多出moveCard屬性,實際上這是實作元件<DealerArea>忘記考慮被拖曳到其他區塊的行為。
所以再次調整DealerArea.vue的樣板,將<draggable的屬性:move對應到函數props.moveCard製作額外判斷是否可以被移出的函數,目前預設為回傳false的函數可以阻止元件內部<draggable>內的元素被移入到別的<draggable元件。
在樣板中使用
props中的屬性moveCard為空,則會自動套用props屬性定義的預設值default
// DealerArea.vue
<!-- 移牌區 - 左邊水平疊牌最多三張 -->
<draggable :list="canTakeCards" group="pokers" itemKey="value" class="list-group" :move="moveCard">
<template #item="{ element, index }">
<Card :value="element.value" :isOpen="element.isOpen" />
</template>
</draggable>
今天將昨天的頁面轉成元件DealerArea並添加一些CSS動畫,也學到子元件需要監聽外部props變動重新渲染的前端做法。
明日將實作讓<DealerArea>內移牌區內的牌有辦法實際拖曳至中間的7個牌堆上並一樣遵守拖曳的遊戲規則。
程式碼: https://github.com/kabuto412rock/ithelp-pokergame/tree/day15